<?php
###
#  Baing PHP 框架 1.0.0
#  http://baing.co/
#
#  Copyright: 白晓阳
#  License: CC BY-NC-SA 4.0
#  https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh
###

namespace bai\resource\application;

use bai\feature\RuntimeException, bai\feature\ResourceException;
use bai\feature\Agent;
use bai\resource\document\Temp;


class Upload extends \bai\resource\Application
{
    public const RESOURCE_DEFAULT = 'resource';

    protected static function setupId(array &$runtime, string $key = ''): string
    {
        return parent::setupId($runtime, $runtime['path'] ?? $key);
    }

    public function run(array $runtime = [])
    {
        $method = $runtime['method'] ?? self::$request->method;
        $call = strtolower($method);
        $resource = $runtime['resource'] ?? self::RESOURCE_DEFAULT;
        return $this->$call($resource, $runtime[$resource] ?? '');
    }

    protected function get(string $resource = self::RESOURCE_DEFAULT, string $fileName = ''): array
    {
        if (!$resource) return self::RUN_FAILED;

        $uploadId = $this->prepareId($resource);
        $last = Agent::session($uploadId) ?? [];
        if (!$fileName) {
            return [
                'status' => self::SUCCEEDED,
                'result' => $last
            ];
        }

        if (empty($last[$fileName])) return self::RUN_FAILED;
        $root = $this->find('path');
        $path = "$root/{$last[$fileName]}";
        if (!is_file($path)) return self::RUN_FAILED;
        self::$response->addResource(Temp::setup(['path' => $path]));
        return self::RUN_SUCCEEDED;
    }

    protected function post(string $resource = self::RESOURCE_DEFAULT): array
    {
        if (!$resource) return self::RUN_FAILED;

        $file = Agent::file($resource);
        if (!$file || empty($file['tmp_name']) || $file['error'] !== UPLOAD_ERR_OK) {
            return self::RUN_FAILED;
        }

        $uploadId = $this->prepareId($resource);
        $last = Agent::session($uploadId) ?? [];
        if (isset($last[$file['name']])) {
            $this->delete($resource, $file['name']);
        }
        $last[$file['name']] = $this->moveUpload($uploadId, $file['tmp_name'], $file['name']);

        Agent::session($uploadId, $last);
        return [
            'status' => self::SUCCEEDED,
            'result' => $last
        ];
    }

    protected function patch(string $resource = self::RESOURCE_DEFAULT, string $fileName = ''): array
    {
        if (!$resource) return self::RUN_FAILED;

        $uploadId = $this->prepareId($resource);
        $last = Agent::session($uploadId) ?? [];
        if (!$last) return self::RUN_FAILED;
        if ($fileName && empty($last[$fileName])) return self::RUN_FAILED;

        $root = $this->find('path');
        $targetRoot = $this->find('target') ?: $root;
        $saveId = $this->prepareId($resource, false);
        $result = [];
        if ($fileName) {
            $path = $last[$fileName];
            $targetPath = str_replace($uploadId, $saveId, $path);
            $result[$fileName] = rename("$root/$path", "$targetRoot/$targetPath");
            if ($result[$fileName]) {
                unset($last[$fileName]);
                if ($last) {
                    Agent::session($uploadId, $last);
                } else {
                    Agent::removeSession($uploadId);
                }
                return [
                    'status' => self::SUCCEEDED,
                    'result' => $result
                ];
            }
            return [
                'status' => self::FAILED,
                'result' => $result
            ];
        }

        foreach ($last as $file => $path) {
            $targetPath = str_replace($uploadId, $saveId, $path);
            $result[$file] = rename("$root/$path", "$targetRoot/$targetPath");
        }
        foreach ($result as $file => $status) {
            if ($status) unset($last[$file]);
        }
        if (!$last) {
            Agent::removeSession($uploadId);
            return [
                'status' => self::SUCCEEDED,
                'result' => $result
            ];
        }
        Agent::session($uploadId, $last);
        return [
            'status' => count($last) < count($result) ? self::INCOMPLETE : self::FAILED,
            'result' => $result
        ];
    }

    protected function delete(string $resource = self::RESOURCE_DEFAULT, string $fileName = ''): array
    {
        if (!$resource) return self::RUN_FAILED;

        $uploadId = $this->prepareId($resource);
        $last = Agent::session($uploadId) ?? [];
        if (!$last) return self::RUN_SUCCEEDED;
        if ($fileName && empty($last[$fileName])) return self::RUN_SUCCEEDED;

        $root = $this->find('path');
        $result = [];
        if ($fileName) {
            $path = $last[$fileName];
            $result[$fileName] = unlink("$root/$path");
            if ($result[$fileName]) {
                unset($last[$fileName]);
                if ($last) {
                    Agent::session($uploadId, $last);
                } else {
                    Agent::removeSession($uploadId);
                }
                return [
                    'status' => self::SUCCEEDED,
                    'result' => $result
                ];
            }
            return [
                'status' => self::FAILED,
                'result' => $result
            ];
        }

        foreach ($last as $file => $path) {
            $result[$file] = unlink("$root/$path");
        }
        foreach ($result as $file => $status) {
            if ($status) unset($last[$file]);
        }
        if (!$last) {
            Agent::removeSession($uploadId);
            return [
                'status' => self::SUCCEEDED,
                'result' => $result
            ];
        }
        Agent::session($uploadId, $last);
        return [
            'status' => count($last) < count($result) ? self::INCOMPLETE : self::FAILED,
            'result' => $result
        ];
    }

    protected function init()
    {
        $path = $this->find('path') ?? self::config(['path', 'application', self::className()]);
        if (!$path) {
            throw new RuntimeException('path');
        }
        if (!is_dir($path) || !is_writable($path)) {
            throw new ResourceException(params: $path);
        }
        $this->runtime['path'] = $path;

        $target = $this->find('target');
        if ($target && (!is_dir($target) || !is_writable($path))) {
            throw new ResourceException(params: $target);
        }
    }

    protected function prepareId(string $resource, bool $pending = true): string
    {
        $name = [];
        if ($pending) {
            $name[] = self::className();
        }
        $name[] = $resource;
        $name[] = Agent::session();
        return join('_', $name);
    }

    protected function moveUpload(string $uploadId, string $from, string $fileName): ?string
    {
        $root = $this->find('path');
        $time = (new \DateTime())->format('YmdHisv');
        $file = "{$uploadId}_{$time}_{$fileName}";
        if (move_uploaded_file($from, "$root/$file")) {
            return $file;
        }
        return null;
    }
}
